

#include <cegfx/ceterrainmesh.h>


ceTerrainMesh::ceTerrainMesh ()
	: vertices(0)
	, solver (0)
{
}

ceTerrainMesh::~ceTerrainMesh ()
{
	delete [] vertices;
}


IQF_IMPLEMENTATION_BEGIN(ceTerrainMesh);
IQF_IMPLEMENTATION_INTERFACE(iGeometry);
IQF_IMPLEMENTATION_END();

bool ceTerrainMesh::Initialize (LPDIRECT3DDEVICE9 device, const float* heights, float size, TerrainSize ts, TerrainPatchSize tps)
{
	void* data;

	D3DVERTEXELEMENT9 elements [] = {
		{ 0,   0, D3DDECLTYPE_FLOAT3,		D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION,		0 },
		{ 0,  12, D3DDECLTYPE_FLOAT3,	  D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL,			0 },
		{ 0,  24, D3DDECLTYPE_FLOAT2,	  D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD,   0 },
		D3DDECL_END ()
	};

	if (FAILED(device->CreateVertexDeclaration (elements, &vertexDeclaration))) return false;
	stride = sizeof (Vertex);

	this->ts = ts;
	this->tps = tps;

	// calc the number of patches per row and for the grid
	unsigned np = (ts-1) / (tps-1);
	numPatches = np * np;


	unsigned numUpperLevelVertices = ts * ts;
	unsigned numLowLevelVertices = (np+1) * ts;
	numVertices = numUpperLevelVertices + 2 * numLowLevelVertices;
	vertices = new Vertex[numVertices];

	Vertex* vptr = vertices;

	float lowerLevelDisplacement = size / 25.0f;
	float x0 = -size;
	float z0 = -size;
	float dx = 2.0f * size;
	float dz = 2.0f * size;
	const float* hptr = heights;
	bbox.Clear ();
	for (unsigned z=0; z<ts; z++)
	{
		float fz = (float)z / (float)(ts-1);
		bool ullz = (z % (tps-1)) == 0;

		for (unsigned x=0; x<ts; x++, vptr++, hptr++)
		{
			float fx = (float)x / (float)(ts-1);
			bool ullx = (x % (tps-1)) == 0;

			vptr->x = x0 + fx * dx;
			vptr->y = *hptr;
			vptr->z = z0 + fz * dz;
			vptr->nx = 0.0f;
			vptr->ny = 1.0f;
			vptr->nz = 0.0f;
			vptr->tu = fx * 10.0f;
			vptr->tv = fz * 10.0f;
			bbox.AddVector3(vptr->x, vptr->y, vptr->z);
		}
	}
	bbox.Update();

	unsigned hStart = numUpperLevelVertices;
	unsigned vStart = numUpperLevelVertices + numLowLevelVertices;
	Vertex* hvptr = &vertices [hStart];
	Vertex* vvptr = &vertices [vStart];
	unsigned offset;
	for (unsigned i=0; i<np+1; i++)
	{
		unsigned ovi = i * (tps-1); // origin value index (column or row)
		for (unsigned j=0; j<ts; j++, vvptr++, hvptr++)
		{
			// calc the horizontals
			offset = ovi * ts + j;
			vptr = &vertices[offset];
			memcpy (hvptr, vptr, sizeof (Vertex));
			hvptr->y -= lowerLevelDisplacement;
			hvptr->tv += 0.25f;

			offset = j * ts + ovi;
			vptr = &vertices[offset];
			memcpy (vvptr, vptr, sizeof (Vertex));
			vvptr->y -= lowerLevelDisplacement;
			vvptr->tu += 0.25f;
		}
	}

	if (FAILED(device->CreateVertexBuffer (sizeof (Vertex) * numVertices, 0, 0, D3DPOOL_DEFAULT, &vertexBuffer, 0))) return false;
	if (FAILED(vertexBuffer->Lock(0, sizeof(Vertex)*numVertices, &data, 0))) return false;
	memcpy (data, vertices, sizeof (Vertex) * numVertices);
	vertexBuffer->Unlock();



	patches = new Patch[numPatches];
	Patch* patch = patches;
	ZeroMemory (patch, sizeof (Patch) * numPatches);

	for (unsigned z=0; z<np; z++)
	{
		// lower level z offset
		unsigned llzo = z * (ts + (np+1) * (tps-2));
		for (unsigned x=0; x<np; x++, patch++)
		{
			patch->x0			= x * (tps-1);
			patch->z0			= z * (tps-1);
			patch->offset = patch->z0 * ts + patch->x0;
			patch->step		= 1;
			patch->llhOffset = hStart + z * ts + x * (tps-1);
			patch->llvOffset = vStart + x * ts + z * (tps-1);

			unsigned numIndices = (tps-1)*(tps-1)*6;
			numIndices += (tps-1) * 6 * 4;
			
			if (numVertices > 65536)
			{
				patch->highRes = true;
				if (FAILED(device->CreateIndexBuffer (sizeof (unsigned) * numIndices, 0, D3DFMT_INDEX32, D3DPOOL_DEFAULT, &patch->indexBuffer, 0))) return false;
			}
			else
			{
				patch->highRes = false;
				if (FAILED(device->CreateIndexBuffer (sizeof (unsigned short) * numIndices, 0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &patch->indexBuffer, 0))) return false;
			}
			UpdatePatchDimensions(patch);
			if (!UpdatePatchIndices (patch)) return false;
		}
	}
	

	return true;
}

#define TRIG(p, a,b,c) *p++ = a; *p++ = b; *p++ = c
#define QUAD(p, a,b,c,d) TRIG(p, a,b,c); TRIG(p, a,c,d)
#define QUAD_HLR(hr, ihr, ilr, a, b, c, d) if (hr) { QUAD(ihr, a, b, c, d); } else { QUAD(ilr, a, b, c, d); }


bool ceTerrainMesh::UpdatePatchIndices (ceTerrainMesh::Patch* patch)
{
	void *data;
	unsigned numTrigsPerRow = (tps-1) / patch->step;
	patch->numTriangles = (numTrigsPerRow * numTrigsPerRow 
												+ 4 * numTrigsPerRow) * 2;

	unsigned size = sizeof (unsigned short);
	if (patch->highRes)
	{
		size = sizeof (unsigned);
	}


	if (patch->indexBuffer->Lock (0, patch->numTriangles * 3 * size, &data, 0)) return false;
	unsigned *iptr_hr = (unsigned*)data;
	unsigned short* iptr_lr = (unsigned short*)data;

	for (unsigned z=0; z<numTrigsPerRow; z++)
	{
		int i0 = (patch->z0 + z * patch->step) * ts;
		int i1 = (patch->z0 + (z+1) * patch->step) * ts;

		for (unsigned x=0; x<numTrigsPerRow; x++)
		{
			int i00 = i0 + patch->x0 + x * patch->step;
			int i10 = i1 + patch->x0 + x * patch->step;

			int i01 = i00 + patch->step;
			int i11 = i10 + patch->step;
			QUAD_HLR(patch->highRes, iptr_hr, iptr_lr, i00, i10, i11, i01);
		}
	
		unsigned i00 = patch->z0 * ts + patch->x0 + z * patch->step;
		unsigned i01 = i00 + patch->step;
		unsigned i10 = patch->llhOffset + z * patch->step;
		unsigned i11 = i10 + patch->step;
		QUAD_HLR(patch->highRes, iptr_hr, iptr_lr, i00, i01, i11, i10);

		i00 = (patch->z0 + tps - 1) * ts + patch->x0 + z * patch->step;
		i01 = i00 + patch->step;
		i10 = patch->llhOffset + ts + z * patch->step;
		i11 = i10 + patch->step;
		QUAD_HLR(patch->highRes, iptr_hr, iptr_lr, i00, i10, i11, i01);

		i00 = (patch->z0 + z * patch->step) * ts + patch->x0;
		i01 = i00 + patch->step * ts;
		i10 = patch->llvOffset + z * patch->step;
		i11 = i10 + patch->step;
		QUAD_HLR(patch->highRes, iptr_hr, iptr_lr, i00, i10, i11, i01);

		i00 = (patch->z0 + z * patch->step) * ts + patch->x0 + tps - 1;
		i01 = i00 + patch->step * ts;
		i10 = patch->llvOffset + ts + z * patch->step;
		i11 = i10 + patch->step;
		QUAD_HLR(patch->highRes, iptr_hr, iptr_lr, i00, i01, i11, i10);
	}

	patch->indexBuffer->Unlock();
	return true;
}

void ceTerrainMesh::UpdatePatchDimensions(ceTerrainMesh::Patch* patch)
{
	patch->bbox.Clear();
	unsigned o0=0;
	for (unsigned z=0; z<tps; z++)
	{
		o0 = (patch->z0 + z) * ts + patch->x0;
		Vertex* vptr = &vertices [o0];
		for (unsigned x=0; x<tps; x++, vptr++)
		{
			patch->bbox.AddVector3 (vptr->x, vptr->y, vptr->z);
		}
	}
	patch->bbox.Update();
}

void ceTerrainMesh::SetLODSolver (iLODSolver* solver)
{
	SAVE_SET(this->solver, solver);
}

iLODSolver* ceTerrainMesh::GetLODSolver ()
{
	return solver;
}

const iLODSolver* ceTerrainMesh::GetLODSolver () const
{
	return solver;
}


void ceTerrainMesh::Update (const ceUpdateEnv& env)
{
	for (unsigned i=0; i<numPatches; i++)
	{
		Patch* patch = &patches[i];

		D3DXVECTOR4 globalPos;
		D3DXVec3Transform (&globalPos, &patch->bbox.GetCenter(), &env.NodeMatrix);
		D3DXVec3Subtract ((D3DXVECTOR3*)&globalPos, (const D3DXVECTOR3*)&globalPos, &env.CameraPosition);
		float d = D3DXVec3Length ((const D3DXVECTOR3*)&globalPos);

		unsigned v = d / 200.0f;
		if (v > 31) v = 31;
		v = 1 << v;
		if (v > (tps-1)) v = (tps-1);
		
		if (patch->step != v)
		{
			patch->step = v;
			UpdatePatchIndices (patch);
		}
	}
}

void ceTerrainMesh::Render(const ceRenderEnv& env)
{
	LPDIRECT3DDEVICE9 device = env.device;
	device->SetVertexDeclaration (vertexDeclaration);
	device->SetStreamSource (0, vertexBuffer, 0, stride);
	Patch* patch = patches;
	for (unsigned i=0; i<numPatches; i++, patch++)
	{
		device->SetIndices (patch->indexBuffer);
		device->DrawIndexedPrimitive (D3DPT_TRIANGLELIST, 0, 0, numVertices, 0, patch->numTriangles);
	}
}

const ceBoundingBox& ceTerrainMesh::GetBoundingBox () const
{
	return bbox;
}



D3DXVECTOR3 ceTerrainMesh::GetVertexPosition (unsigned x, unsigned z) const
{
	if (x >= ts || z >= ts)
	{
		return D3DXVECTOR3(0, 0, 0);
	}

	Vertex& vptr = vertices [z * ts + x];

	return D3DXVECTOR3 (vptr.x, vptr.y, vptr.z);
}



D3DXVECTOR3 ceTerrainMesh::GetPosition (float x, float z) const
{
	if (x < 0.0f || x > 1.0f || z < 0.0f || z > 1.0f)
	{
		return D3DXVECTOR3(0, 0, 0);
	}

	// the real values are the [0,1] interval expanded to [0, ts]
	float rx = x * ts;
	float rz = z * ts;

	// floor the result to get the left and top positions
	unsigned x0 = (unsigned)rx;
	unsigned z0 = (unsigned)rz;

	// add one row and column to get the right and bottom positions
	unsigned x1 = x0 + 1;
	unsigned z1 = z0 + 1;

	if (x1 >= ts) x1 = x0;
	if (z1 >= ts) z1 = z0;

	// the interpolation values
	float ix = rx - x0;
	float iz = rz - z0;



	// the the 4 bounding vertices
	D3DXVECTOR3 v00 = GetVertexPosition(x0, z0);
	D3DXVECTOR3 v01 = GetVertexPosition(x1, z0);
	D3DXVECTOR3 v10 = GetVertexPosition(x0, z1);
	D3DXVECTOR3 v11 = GetVertexPosition(x1, z1);

	// the x and z coordinate can be simply interpolated
	float fx = v00.x + ix * (v01.x - v00.x);
	float fz = v00.z + iz * (v10.z - v00.z);

	// the y value must be interplated bi-linear 

	float h0 = v00.y + iz * (v10.y - v00.y);
	float h1 = v01.y + iz * (v11.y - v01.y);
	float fy = h0 + ix * (h1 - h0);


	return D3DXVECTOR3 (fx, fy, fz);
}
